/*=============================================================================
PostProcess.h : Post processing techniques base interface
Copyright (c) 2001 Crytek Studios. All Rights Reserved.

Revision history:
* 18/06/2005: Re-organized (to minimize code dependencies/less annoying compiling times)
* 23/02/2005: Re-factored/Converted to CryEngine 2.0
* Created by Tiago Sousa

=============================================================================*/

#ifndef _POSTPROCESS_H_
#define _POSTPROCESS_H_

class CShader;
class CTexture;


// Declare effects ID - this will also be used as rendering sort order
enum EPostEffectID
{	
	ePFX_Invalid = -1,
	ePFX_WaterVolume = 0,
	ePFX_WaterRipples,

	ePFX_eScreenFog,

  ePFX_SceneRain,
  ePFX_eGlow,
	
	// Don't change order of post processes before sunshafts (on ps3/pc we doing some trickery to avoid redundant stretchrects)
	ePFX_SunShafts,				
	ePFX_eMotionBlur,	
	ePFX_ColorGrading,
	ePFX_eDepthOfField,
	ePFX_HUDSilhouettes,		
	
	ePFX_PostAA,

	ePFX_eSoftAlphaTest,
	ePFX_SceneSnow,

	ePFX_eUnderwaterGodRays,
	ePFX_eVolumetricScattering,
	
	// todo: merge all following into UberGamePostProcess
	ePFX_FilterSharpening,
	ePFX_FilterBlurring,
	ePFX_UberGamePostProcess,

	ePFX_NightVision,
	ePFX_SonarVision,
	ePFX_ThermalVision,

	ePFX_eFlashBang,

	ePFX_ImageGhosting,
	ePFX_NanoGlass,
	ePFX_HUDHitEffect,

	ePFX_eRainDrops,
	ePFX_eWaterDroplets,
	ePFX_eWaterFlow,
	ePFX_eScreenBlood,
	ePFX_eScreenGlassShards,
	ePFX_eScreenFrost,


	ePFX_FilterKillCamera,
	ePFX_eAlienInterference,

	ePFX_PostStereo,
	ePFX_3DHUD,

	ePFX_GammaReference,

	ePFX_Post3DRenderer,
	
	ePFX_Max
};

//////////////////////////////////////////////////////////////////////
enum ERenderModeFlags
{
	eRMF_THERMALVISION = (1<<0),
	eRMF_SONARVISION = (1<<1),
	eRMF_NIGHTVISION = (1<<2),
	eRMF_MASK = eRMF_THERMALVISION | eRMF_SONARVISION | eRMF_NIGHTVISION
};

// Base effect parameter class, derive all new from this one
class CEffectParam
{
public:

  CEffectParam(){ }

  virtual ~CEffectParam()
  {
    Release();
  }

  // Should implement where necessary. For example check CParamTexture
  virtual void Release() { }

  // Set parameters
  virtual void SetParam(float fParam, bool bForceValue=false) {}    
  virtual void SetParamVec4(const Vec4 &pParam, bool bForceValue=false) {}
  virtual void SetParamString(const char *pszParam) {}
  virtual void ResetParam(float fParam) { SetParam(fParam, true); }
  virtual void ResetParamVec4(const Vec4 &pParam) { SetParamVec4(pParam, true); }

  // Get parameters
  virtual float GetParam()  { return 1.0f; }
  virtual Vec4 GetParamVec4()  { return Vec4(1.0f, 1.0f, 1.0f, 1.0f); }
  virtual const char *GetParamString() const { return 0; }

	// Sync main thread data with render thread data
	virtual void SyncMainWithRender() {}

  // Create effect parameter
  template <typename ParamT, typename T> static CEffectParam *Create(const T &pParam, bool bSmoothTransition = true)     
  {
    return new ParamT(pParam, bSmoothTransition);
  }
};

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

// Bool type effect param
class CParamBool: public CEffectParam
{
public:
  CParamBool(bool bParam, bool bSmoothTransition) 
  { 
    m_bParam=bParam;
  }

  virtual void SetParam(float fParam, bool bForceValue)
  {
    m_bParam=(fParam)?1:0;
  }

  virtual float GetParam() 
  {
    return static_cast<float>(m_bParam);
  }    

private:
  bool m_bParam;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

// Int type effect param
class CParamInt: public CEffectParam
{
public:
  CParamInt(int nParam, bool bSmoothTransition) 
  { 
    m_nParam=nParam;
  }

  virtual void SetParam(float fParam, bool bForceValue)
  {
    m_nParam=static_cast<int>(fParam);
  }

  virtual float GetParam()
  {
    return static_cast<float>(m_nParam);
  }

private:
  int m_nParam;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

// Float type effect param
class CParamFloat: public CEffectParam
{
public:
  CParamFloat(float fParam, bool bSmoothTransition)
  { 
    m_bSmoothTransition = bSmoothTransition;
    m_fParam = m_fParamDefault = m_fFrameParamAcc = fParam;
    m_nFrameSetCount = 0;
  }

  virtual void SetParam(float fParam, bool bForceValue);
  virtual float GetParam();
	virtual void SyncMainWithRender();

private:
  float m_fParam;

  bool m_bSmoothTransition;
  float m_fParamDefault;
  float m_fFrameParamAcc;
  uint8 m_nFrameSetCount;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

// Vec4 type effect param
class CParamVec4: public CEffectParam
{
public:
  CParamVec4(const Vec4 &pParam, bool bSmoothTransition) 
  { 
    m_bSmoothTransition = bSmoothTransition;
    m_pParam = m_pParamDefault = m_pFrameParamAcc = pParam;
    m_nFrameSetCount = 0;
  }

  virtual void SetParamVec4( const Vec4 &pParam, bool bForceValue );
  virtual Vec4 GetParamVec4();
	virtual void SyncMainWithRender();

private:
  Vec4 m_pParam;
  
  bool m_bSmoothTransition;
  Vec4 m_pParamDefault;
  Vec4 m_pFrameParamAcc;
  uint8 m_nFrameSetCount;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

// CTexture type effect param
class CParamTexture: public CEffectParam
{
public:
  CParamTexture(): m_pTexParam(0)
  { 
  }

  CParamTexture(int nInit, bool bSmoothTransition): m_pTexParam(0)
  { 
  }

  virtual ~CParamTexture()
  { 
    Release();
  }

  // Create texture
  int Create(const char *pszFileName);
  // Release resources
  virtual void Release();

  virtual void SetParamString(const char *pParam)
  {
    Create(pParam);
  }

  virtual const char *GetParamString() const;
  const CTexture *GetParamTexture() const
  {
    return m_pTexParam;
  }    

private:
  CTexture *m_pTexParam;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

// Post processing render flags
enum EPostProcessRenderFlag
{  
  PSP_UPDATE_BACKBUFFER = ( 1 << 0 ),					// updates back-buffer texture for technique
  PSP_REQUIRES_UPDATE = ( 1 << 1 ),						// required calling update member function
	PSP_LAST_ENABLED_POSTPROCESS = ( 1 << 2 ),	// last enabled post process (used to determine if rendering directly to backbuffer required)
};

// Post effects base structure interface. All techniques derive from this one.
class CPostEffect
{    
public:
	CPostEffect(): m_pActive(0), /*m_nID(ePFX_DefaultID),*/ m_nRenderFlags( PSP_UPDATE_BACKBUFFER )
	{ 
	}

	virtual ~CPostEffect()
	{          
		Release();
	}

  // Initialize post processing technique - device access allowed (queries, ...)
  virtual int Initialize() { return 1; }
	// Create all the resources for the pp effects which don't require the device (such as textures)
	virtual int CreateResources() { return 1; }
  // Free resources used
  virtual void Release() { }
  // Preprocess technique
  virtual bool Preprocess() { return IsActive(); }
  // Some effects might require updating data/parameters, etc
  virtual void Update() { };
  // Render technique
  virtual void Render()=0;
  // Reset technique state to default
  virtual void Reset(bool bOnSpecChange = false)=0;
  // release resources when required
  virtual void OnLostDevice() { }

	// Add render element/object to post process (use for custom geometry)
	virtual void AddRE(const CRendElementBase* pRE, const SShaderItem* pShaderItem, CRenderObject* pObj) { }
	// release resources when required
	virtual void OnBeginFrame() { }

  // Get technique render flags
  int GetRenderFlags() const
  {
    return m_nRenderFlags;
  }

  // Get effect name
  virtual const char *GetName() const
  {
    return "PostEffectDefault";
  }

  // Is technique active ?
  virtual bool IsActive() const
  {
		float fActive = m_pActive->GetParam();
		return (fActive)?1:0;
  }

	inline uint8 GetID() const
	{
		return m_nID;
	}

  // RenderData for DrawCurvedScreenSurface()
  struct SRenderData
  {
	const CRendElementBase*	pRenderElement;
	const CRenderObject*	pRenderObject;
	SRenderData(): pRenderElement(NULL), pRenderObject(NULL){}    
  };
  typedef	SRenderData	SRenderDataAr[RT_COMMAND_BUF_COUNT];
  
  // Draws full-screen curved surface  
  virtual void DrawGeometry(SRenderDataAr* pRenderDataAr, CShader* pShader, const CCryNameTSCRC& nameTech);

protected:      
  uint8 m_nRenderFlags;    
	uint8 m_nID;
  CEffectParam *m_pActive;
};

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

typedef std::map<string, CEffectParam *> StringEffectMap;    
typedef StringEffectMap::iterator StringEffectMapItor;

typedef std::map<uint32, CEffectParam *> KeyEffectMap;    
typedef KeyEffectMap::iterator KeyEffectMapItor;

typedef std::vector< CPostEffect* > CPostEffectVec;
typedef CPostEffectVec::iterator CPostEffectItor;

class CPostEffectsMgr;
static CPostEffectsMgr* PostEffectMgr();

// Post process effects manager
class CPostEffectsMgr
{    
public:
	CPostEffectsMgr(): m_nPostBlendEffectsFlags(0)
	{
    ClearCache();
    m_bCreated = false;
	}

	virtual ~CPostEffectsMgr()
	{     
		Release();
	}

  // Create/Initialize post processing effects 
  int  Init();
  void CreateResources();
  // Free resources used
  void Release();
  void ReleaseResources();
  // Reset all post effects
  void Reset(bool bOnSpecChange = false);
  // Start processing effects
  void Begin();
  // End processing effects
  void End();  
  // release resources when required
  void OnLostDevice();
	// release resources when required
	void OnBeginFrame();
	// Sync main thread post effect data with render thread post effect data
	void SyncMainWithRender();

  // Get techniques list
  CPostEffectVec &GetEffects()
  {
    return m_pEffects;
  }
  inline bool IsCreated()
  {
    return m_pEffects.size() != 0;
  }

	// Get post effect
	CPostEffect *GetEffect(EPostEffectID nID)
	{
		assert( nID < ePFX_Max );
		return m_pEffects[ nID ];
	}

	// Get post effect ID
	int32 GetEffectID(const char* pEffectName);

  // Get name to id map
  KeyEffectMap &GetNameIdMap()
  {
		int nThreadID = gRenDev->m_pRT ? gRenDev->m_pRT->GetThreadList() : 0;
    return m_pNameIdMap[nThreadID];
  }

  // Given a string returns corresponding SEffectParam if exists, else returns null
  CEffectParam *GetByName(const char *pszParam);

  // Given a string returns containing value if exists, else returns 0
  float GetByNameF(const char *pszParam);
  Vec4 GetByNameVec4(const char *pszParam);
  
	// Register effect
	void RegisterEffect( CPostEffect *pEffect )
	{
		assert( pEffect );
		m_pEffects.push_back( pEffect );
	}

	// Register a parameter
	template <typename paramT, typename T> 
	void RegisterParam(const char *pszName, CEffectParam *&pParam, const T &pParamVal, bool bSmoothTransition = true)
	{
		pParam = CEffectParam::Create< paramT >( pParamVal, bSmoothTransition );
		m_pNameIdMapGen.insert( StringEffectMapItor::value_type( pszName, pParam) );  
	}
  
  // Current enabled post blending effects
  uint8 GetPostBlendEffectsFlags()
  {
    return m_nPostBlendEffectsFlags;
  }

  // Enabled/disable post blending effects
  void SetPostBlendEffectsFlags( uint8 nFlags )
  {
    m_nPostBlendEffectsFlags = nFlags;
  }

  friend CPostEffectsMgr* PostEffectMgr();

  static bool CheckPostProcessQuality( ERenderQuality nMinRQ, EShaderQuality nMinSQ )
  {
    if( gRenDev->m_RP.m_eQuality >= nMinRQ && gRenDev->EF_GetShaderQuality(eST_PostProcess) >= nMinSQ )
      return true;

    return false;
  }

  StringEffectMap *GetDebugParamsUsedInFrame()
  {
		int nThreadID = gRenDev->m_pRT ? gRenDev->m_pRT->GetThreadList() : 0;
    return &m_pEffectParamsUpdated[nThreadID];
  }

	static int SortEffectsByID(const CPostEffect *p1, const CPostEffect *p2);

  void AddLight( SRenderLight &pLight)
  {
#ifndef _RELEASE
    if (SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nFillThreadID] == 0) __debugbreak();
#endif 
    if( gRenDev->CV_r_PostProcess )
      m_pPostProcLights[gRenDev->m_RP.m_nFillThreadID][SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nFillThreadID] - 1].AddElem(pLight);     
  }

  TArray<SRenderLight> &GetLights()
  {
#ifndef _RELEASE
    if (SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID] == 0) __debugbreak();
#endif 
    return m_pPostProcLights[gRenDev->m_RP.m_nProcessThreadID][SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID] - 1];
  }

  void ResetLights()
  {
#ifndef _RELEASE
    if (SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID] == 0) __debugbreak();
#endif 
    m_pPostProcLights[gRenDev->m_RP.m_nProcessThreadID][SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID] - 1]=TArray<SRenderLight>();
  }
  
	// Get techniques list
  CPostEffectVec &GetActiveEffects(int threadID)
  {
    return m_activeEffects[threadID];
  }

private:      
	
	// Pass a text string to this function and it will return the CRC
  uint32 GetCRC( const char *pszName );
	// Used only when creating the crc table
	uint32	CRC32Reflect(uint32 ref, char ch);
  // zero out the cache
  void ClearCache()
  {
    for(int i=0; i < RT_COMMAND_BUF_COUNT; i++)
    {
		  m_pParamCache[i].m_nKey = 0;
		  m_pParamCache[i].m_pParam = 0;
    }
  }

protected:      

  bool m_bPostReset;
  bool m_bCreated;
  uint8 m_nPostBlendEffectsFlags;

  // Shared parameters
  CEffectParam *m_pBrightness, *m_pContrast, *m_pSaturation, *m_pSharpening;
  CEffectParam *m_pColorC, *m_pColorY, *m_pColorM, *m_pColorK, *m_pColorHue;

  CEffectParam *m_pUserBrightness, *m_pUserContrast, *m_pUserSaturation, *m_pUserSharpening;
	CEffectParam *m_pUserColorC, *m_pUserColorY, *m_pUserColorM, *m_pUserColorK, *m_pUserColorHue;
  CEffectParam *m_pDirectionalBlurVec;

	// sorry: quick & dirty solution for c2 shipping - custom type handling for HDR setup in cinematics - make this properly after shipping
	CEffectParam *m_pUserHDRBrightLevel;
	CEffectParam *m_pUserHDRBrightThreshold;

  CPostEffectVec m_pEffects;
	CPostEffectVec m_activeEffects[RT_COMMAND_BUF_COUNT];
  KeyEffectMap m_pNameIdMap[RT_COMMAND_BUF_COUNT];  
	StringEffectMap m_pNameIdMapGen;
  // for debugging purposes only
  StringEffectMap m_pEffectParamsUpdated[RT_COMMAND_BUF_COUNT];

	uint32 m_nCRC32Table[256];  // Lookup table array 

	TArray<SRenderLight> m_pPostProcLights[RT_COMMAND_BUF_COUNT][MAX_REND_RECURSION_LEVELS];

  struct ParamCache
  {
    uint32 m_nKey;
    CEffectParam* m_pParam;
  } m_pParamCache[RT_COMMAND_BUF_COUNT];
};

static CPostEffectsMgr* PostEffectMgr()
{   
	return gRenDev->m_pPostProcessMgr;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Some nice utilities for handling post effects containers
//////////////////////////////////////////////////////////////////////////////////////////////////

#define AddEffect( ef ) PostEffectMgr()->RegisterEffect( ( new (ef) ) )  
#define AddParamBool( szName, pParam, val ) PostEffectMgr()->RegisterParam<CParamBool, bool>( (szName), (pParam), val)
#define AddParamInt( szName, pParam, val ) PostEffectMgr()->RegisterParam<CParamInt, int>( (szName), (pParam), val)
#define AddParamFloat( szName, pParam, val ) PostEffectMgr()->RegisterParam<CParamFloat, float>( (szName), (pParam), val)
#define AddParamVec4( szName, pParam, val ) PostEffectMgr()->RegisterParam<CParamVec4, Vec4>( (szName), (pParam), val)
#define AddParamFloatNoTransition( szName, pParam, val ) PostEffectMgr()->RegisterParam<CParamFloat, float>( (szName), (pParam), val, false)
#define AddParamVec4NoTransition( szName, pParam, val ) PostEffectMgr()->RegisterParam<CParamVec4, Vec4>( (szName), (pParam), val, false)
#define AddParamTex( szName, pParam, val ) PostEffectMgr()->RegisterParam<CParamTexture, int>( (szName), (pParam), val)

struct container_object_safe_delete
{
  template<typename T>
  void operator()( T* pObj ) const
  {
    SAFE_DELETE( pObj );
  }
};

struct container_object_safe_release
{
  template<typename T>
  void operator()( T* pObj ) const
  {
    SAFE_RELEASE( pObj );
  }
};

struct SContainerKeyEffectParamDelete
{
  void operator()( KeyEffectMap::value_type &pObj )
  {
    SAFE_DELETE( pObj.second );
  }
};

struct SContainerPostEffectInitialize
{    
  void operator() ( CPostEffect *pObj ) const
  {
    if( pObj)
    {
      pObj->Initialize();
    }
  }
};

struct SContainerPostEffectCreateResources
{    
	void operator() ( CPostEffect *pObj ) const
	{
		if( pObj)
		{
			pObj->CreateResources();
		}
	}
};

struct SContainerPostEffectReset
{    
  void operator()( CPostEffect *pObj ) const
  {
    if( pObj )
    {
      pObj->Reset();
    }      
  }
};	

struct SContainerPostEffectResetOnSpecChange
{    
	void operator()( CPostEffect *pObj ) const
	{
		if( pObj )
		{
			pObj->Reset(true);
		}      
	}
};	

struct SContainerPostEffectOnLostDevice
{    
  void operator() ( CPostEffect *pObj ) const
  {
    if( pObj)
      pObj->OnLostDevice();
  }
};

struct SContainerPostEffectOnBeginFrame
{    
	void operator() ( CPostEffect *pObj ) const
	{
		if( pObj)
			pObj->OnBeginFrame();
	}
};

#endif
